package com.bloomingstars.sqm.orm;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.Properties;
import java.util.logging.Logger;

import org.hibernate.HibernateException;
import org.hibernate.MappingException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.StatelessSession;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.ServiceRegistryBuilder;

import com.bloomingstars.sqm.util.JarResourceUtility;

/**
 * General implementation for Hibernate configuration loader class. 
 */
public class HibernateUtil {

    private static final String DataConfigurationFileName = "hibernate.cfg.xml";
    
    // configuration location
    private static System system;

    private static Configuration configuration;    
    private static SessionFactory sessionFactory;
    
    private static final Logger logger = Logger.getLogger(HibernateUtil.class.getName());

    public static System getSystem() {
        return system;
    }

    public static void setSystem(System system) {
        HibernateUtil.system = system;
    }

    private HibernateUtil() {}

    /**
     * Getter for hibernate configuration instance Loads hibernate mapping and
     * configuration files. Uses <code>ConfigurationFolderName</code> as
     * location to look for hibernate configuration
     * 
     * we make the assumption that the config and mapping files are always
     * contained in cnf/hibernate
     * 
     * @return the configuration
     * @throws IOException
     * @throws HibernateException
     */
    public static Configuration getConfiguration() {
        if (configuration == null) {
            configuration = new Configuration();

            configuration.configure(getConfigurationFile());
            getLogger().fine("Adding abstract hibernate APPLICATION mappings");
            addConfiguration(configuration);

            getLogger().fine(
                    "Leaving initialisation of APPLICATION configuration");
        }

        return configuration;

    }

    /**
     * Setter for hibernate configuration instance
     * 
     * @param configuration
     *            the configuration to set
     */
    public static void setConfiguration(Configuration configuration) {
        HibernateUtil.configuration = configuration;
    }

    /**
     * @return the sessionFactory
     */
    public static SessionFactory getSessionFactory() {
        if (sessionFactory == null) {
            ServiceRegistryBuilder serviceRegistryBuilder = new ServiceRegistryBuilder()
                    .applySettings(getConfiguration().getProperties());
            sessionFactory = configuration
                    .buildSessionFactory(serviceRegistryBuilder
                            .buildServiceRegistry());
        }
        return sessionFactory;
    }
    
    /**
     * This method uses Hibernates getCurrentSession to return the session
     * currently bound to this thread If there is no session open, Hibernate
     * opens a session Programmer needs to handle the transaction Hibernate will
     * automatically close this session when a commit is performed
     * 
     * @return the session currently bound to this thread
     */
    public static Session getSession() {
        return getSessionFactory().getCurrentSession();
    }
    
    /**
     * This method opens and returns a new hibernate session Programmer needs to
     * handle the transaction it is the responsibility of the programmer to
     * close the session when they are finished
     * 
     * @return a new hibernate session
     */
    public static Session openNewSession() {
        return getSessionFactory().openSession();
    }

    /**
     * This method returns a stateless Hibernate session
     * 
     * @return a stateless Hibernate session
     */
    public static StatelessSession getStatelessSession() {
        return getSessionFactory().openStatelessSession();
    }

    /**
     * @return
     */
    private static Logger getLogger() {
    return logger;
    }

    /**
     * @return
     */
    public static ClassLoader getClassLoader() {
        return HibernateUtil.class.getClassLoader();
    }

    /**
     * Adds hibernate mapping files to the configuration. Uses class loader to
     * determine the current location of the class and hibernate mapping files.
     * Currently the are to locations supported jar file and local directory.
     * 
     * @param hibernateConfiguration
     *            configuration instance to load the mappings
     * @param hibernateClassLoader
     *            class loader to define class location and to load mapping
     *            resources
     * @throws MappingException
     *             in case of hibernate mapping problems
     */
    public static void addConfiguration(Configuration hibernateConfiguration)
        throws MappingException {
    
        if (hibernateConfiguration == null) {
            logger.severe("Undefined hibernate configuration object. Exiting method");
            return;
        }

        try {
            Enumeration<URL> urls = getClassLoader().getResources("cnf");
            while (urls.hasMoreElements()) {
                URL temp = urls.nextElement();
                logger.info("Raw configuration file URL: <" + temp.toString() + ">");
                logger.info("Raw configuration file path: <" + temp.getPath().toString() + ">");
                if (JarResourceUtility.isInJarFile(getClassLoader(), temp.getFile())) {
                    File tempFile = new File(URLDecoder.decode(temp.getPath()
                        .substring(5, temp.getPath().indexOf(".jar!") + 4),
                        "UTF-8"));
                    
                    logger.info("Decoded URL: <" + tempFile.getAbsolutePath() + ">");
                    if (isOsUnix()) {
                        tempFile = new File("/", tempFile.getAbsolutePath());
                    }                                        
                    logger.info("Trying to add configuration file: <" + tempFile + ">");
                    hibernateConfiguration.addJar(tempFile.getCanonicalFile());
                } else {
                    File file = new File(temp.getFile());
                    hibernateConfiguration.addDirectory(file);
                }
            }
        } catch (IOException ioe) {
            logger.severe("Failed to initialise Hibernate configuration: "
                + ioe.getMessage());
            ioe.printStackTrace();
        }
    }

    /**
     * @return Database connection string (JDBC)
     */
    public static String getConnectionString() {
        String result = null;
        
        if (configuration != null) {
            Properties hibernateProps = configuration.getProperties();
            result = hibernateProps.getProperty("hibernate.connection.url");
        }                                
        
        return result;
    }
    
    /**
     * 
     * @param propertyName Hibernate property name
     * @return Hibernate property value, or null if it does not exist
     */
    public static String getProperty(String propertyName) {
        return getProperty(propertyName, HibernateSystemType.DATA);        
    }
    
    public static String getProperty(String propertyName, HibernateSystemType type) {
        String result = null;
        
        if (configuration != null) {
            result = configuration.getProperties().getProperty(propertyName);
        }
        return result;
    }
    
    /** 
     * @return Name of the schema for the db currently in use 
     */
    public static String getSchemaName() {
        getSession();
        String connString = getConnectionString();
        String result = null;
        
        if (connString.indexOf("/") == -1) { // Oracle
            result = connString.substring(connString.lastIndexOf(":") + 1);
        } else {                             // MySQL
            result = connString.substring(connString.lastIndexOf("/") + 1);
        }
        
        return result;
    }
    
    /**
     * Close current hibernate stateless session
     */
    public static void closeStatelessSession() {
        getStatelessSession().close();
    }
    
    public static void restartHibernate(){
        setSessionFactory(getConfiguration().buildSessionFactory());
    }        
    
    public static final void setSessionFactory(SessionFactory sf) {
        sessionFactory = sf;
    }
    
    /** 
     * @return db type by looking at the JDBC connection string
     */
    public static DatabaseType getDatabaseType() {
        String connString = getConnectionString().toLowerCase();        
        if (connString.indexOf("oracle") != -1) {
            return DatabaseType.ORACLE;
        } else if (connString.indexOf("mysql") != -1) {
            return DatabaseType.MYSQL;
        } else {
            return DatabaseType.UNKNOWN;
        }
    }
    
    /**
     * @return
     */
    private static URL getConfigurationFile() {        
        String fileName = DataConfigurationFileName;
        
        // Configuration file
        URL url = null;
        url = getClassLoader().getResource(
            "cnf/" + fileName);
        if (url != null)
            return url;

        url = getClassLoader().getResource(
            "../src/cnf/" + fileName);

        if (url == null) {
            url = getClassLoader().getResource(fileName);
        }
        
        if (url == null) {
            url = getClassLoader().getResource("hibernate/" + fileName);
        }
        
        String spec = url.toString();
        // get directly from the directory
        URL resourceUrl = null;
        try {
            resourceUrl = new URL(spec);
        } catch (MalformedURLException e) {
            logger.severe("Failed to get configuration file. " + e);
            e.printStackTrace();
        }
        return resourceUrl;         
    }
            
    private static boolean isOsUnix() {
        String os = System.getProperty("os.name").toLowerCase();
        if (os.indexOf("unix") != -1 || os.indexOf("linux") != -1) {
            return true;
        } else {
            return false;
        }
    }    
}